package xcore

import (
	"encoding/json"
	"fmt"
	"reflect"
	"strings"
	"sync"
)

type SafeMarshal struct {
	seen map[uintptr]bool
}

func NewSafeMarshal() *SafeMarshal {
	return &SafeMarshal{
		seen: make(map[uintptr]bool),
	}
}

func (sm *SafeMarshal) marshal(v reflect.Value) interface{} {
	if v.Kind() == reflect.Ptr {
		if v.IsNil() {
			return nil
		}
		v = v.Elem()
	}
	if v.Kind() != reflect.Struct {
		return v.Interface()
	}

	if v.CanAddr() {
		ptr := v.UnsafeAddr()
		if sm.seen[ptr] {
			return nil
		}
		sm.seen[ptr] = true
	}

	result := make(map[string]interface{})
	for i := 0; i < v.NumField(); i++ {
		field := v.Type().Field(i)
		name := field.Name
		if tag, ok := field.Tag.Lookup("json"); ok {
			name = strings.Split(tag, ",")[0]
		} else {
			name = strings.ToLower(name[:1]) + name[1:]
		}
		value := v.Field(i)
		result[name] = sm.marshal(value)
	}
	return result
}

func (sm *SafeMarshal) MarshalJSON(v interface{}) (result []byte, err error) {
	defer func() {
		if r := recover(); r != nil {
			err = fmt.Errorf("panic recovered: %v", r)
		}
	}()
	if v == nil {
		return json.Marshal(v)
	}
	return json.Marshal(sm.marshal(reflect.ValueOf(v)))
}

var defaultSafeMarshalMgr = &SafeMarshalMgr{
	Mutex:       sync.Mutex{},
	safeMashals: make(map[interface{}]*SafeMarshal),
}

type SafeMarshalMgr struct {
	sync.Mutex
	safeMashals map[interface{}]*SafeMarshal
}

func (m *SafeMarshalMgr) getInstance(v interface{}) (*SafeMarshal, error) {
	m.Lock()
	defer m.Unlock()
	mars := m.safeMashals[v]
	if mars == nil {
		mars = NewSafeMarshal()
		m.safeMashals[v] = mars
	}
	return mars, nil
}
func (m *SafeMarshalMgr) MarshalJSON(v interface{}) ([]byte, error) {
	ins, err := m.getInstance(v)
	if err != nil {
		return nil, err
	}
	return ins.MarshalJSON(v)
}
func MarshalJSON(v interface{}) ([]byte, error) {
	return defaultSafeMarshalMgr.MarshalJSON(v)
	//return sonic.Marshal(v)
}
